#include "Sea.h"


Sea::Sea() : flag(true), bFog(false), fog_start(0.0f), counter(0.0f), wave_height(1.0f), bFrozen(false),
			ice_percentage(0.0f), radius(5.0f), srv_sea0_normalmap(0), srv_sea1_normalmap(0),
			srv_ice_heightmap(0), srv_ice(0), fx_m_World(0), fx_m_WorldViewProj(0), fx_m_L_ViewProj(0), fx_tex_shadow_map(0), 
			fx_lights(0), fx_num_lights(0), fx_pEye(0), fx_bShadowed(0), fx_bUsePCSS(0), day_length(10.0), vb_sea11(0),
			bSea11(false)
{
	isShadowCaster(false);
	isShadowReceiver(true);
	setRadius(5.0f);
}
	

Sea::~Sea()
{
	try
	{
		if (srv_sea0_normalmap)
			srv_sea0_normalmap->Release();
		if (srv_sea1_normalmap)
			srv_sea1_normalmap->Release();
		if (srv_ice_heightmap)
			srv_ice_heightmap->Release();
		if (srv_ice)
			srv_ice->Release();
		if (vb_sea11)
			vb_sea11->Release();
		if (mRefractor)
			delete mRefractor;
	}
	catch(...)
	{
		MessageBoxA(0, "exception thrown in  destructor", 0, 0);
	}
}

void Sea::adjustSeaLevel(float day_time)
{
	
	if (ice_percentage > 0.0f || bFrozen)
		return;

	// level update - always
	float low = 1.0f;
	float high = 1.25f;

	// [0,1]
	float p = day_time / day_length;
	// [0,1] -> [0,2pi]
	p *= 2.0f * 3.14f;

	float h = (low + high) / 2.0f; // middle point
	h = h + sin(p) * (high-low) / 2.0f;
	
	setWorldMatrixVal(3, 1, h);
	
	// update spray effect
	_spray.setHeight(h);
}


void Sea::draw(int subset, ID3DX11Effect* fx, const char* technique)
{
	Mesh::draw(0, fx, technique);
}

void Sea::draw(const Camera& camera, const vector<Light*>& lights)
{
	
	if (bFog) {
		ID3DX11EffectScalarVariable* fx_time = getFX()->GetVariableByName("border")->AsScalar();
		fx_time->SetFloat(counter-fog_start);
	}

	ID3DX11EffectScalarVariable*	fx_waveAngle = getFX()->GetVariableByName("waveAngle")->AsScalar();
	fx_waveAngle->SetFloat(this->angle);


	ID3DX11EffectScalarVariable* fx_wave_t = getFX()->GetVariableByName("wave_t")->AsScalar();
	fx_wave_t->SetFloat(counter);

	ID3DX11EffectVectorVariable* fx_windDir = getFX()->GetVariableByName("windDir")->AsVector();
	fx_windDir->SetFloatVector((float*)&m_windDirection);

	ID3DX11EffectScalarVariable* fx_wave_height = getFX()->GetVariableByName("wave_height")->AsScalar();
	fx_wave_height->SetFloat(wave_height);

	ID3DX11EffectScalarVariable* fx_windPower = getFX()->GetVariableByName("fWindPower")->AsScalar();
	fx_windPower->SetFloat(this->m_windPower);

	ID3DX11EffectScalarVariable* fx_ice_percentage = getFX()->GetVariableByName("ice_percentage")->AsScalar();
	fx_ice_percentage->SetFloat(ice_percentage);

	
	ID3DX11EffectShaderResourceVariable* fx_tex_ice_heightmap = getFX()->GetVariableByName("tex_ice_heightmap")->AsShaderResource();
	fx_tex_ice_heightmap->SetResource(srv_ice_heightmap);

	ID3DX11EffectShaderResourceVariable* fx_tex_ice = getFX()->GetVariableByName("tex_ice")->AsShaderResource();
	fx_tex_ice->SetResource(srv_ice);


	// pass texture, width, height
	ID3DX11EffectScalarVariable* fx_reflection_tex_width = getFX()->GetVariableByName("reflection_tex_width")->AsScalar();
	fx_reflection_tex_width->SetInt(getWidth());

	ID3DX11EffectScalarVariable* fx_reflection_tex_height = getFX()->GetVariableByName("reflection_tex_height")->AsScalar();
	fx_reflection_tex_height->SetInt(getHeight());


	static float tex_offset = 0.0f;
	tex_offset += 0.001f;

	ID3DX11EffectScalarVariable*	fx_radius = getFX()->GetVariableByName("radius")->AsScalar();
	fx_radius->SetFloat(radius);

	////////////////////////////////////////////////////////////

	ID3DX11EffectShaderResourceVariable*	fx_tex_normal0 = getFX()->GetVariableByName("tex_sea0_normalmap")->AsShaderResource();
	fx_tex_normal0->SetResource(srv_sea0_normalmap);

	ID3DX11EffectShaderResourceVariable*	fx_tex_normal1 = getFX()->GetVariableByName("tex_sea1_normalmap")->AsShaderResource();
	fx_tex_normal1->SetResource(srv_sea1_normalmap);

	//////////////////



	// Material
	Mesh::setMaterial(getFX());

	// World Matrix
	XMMATRIX mWorld = XMLoadFloat4x4(&(mtxWorld()));
	fx_m_World->SetMatrix((float*)&mWorld);
	fx_m_WorldViewProj->SetMatrix((float*)&(mWorld * camera.getViewMatrix() * camera.getProjectionMatrix()));
	fx_pEye->SetFloatVector((float*)&(camera.getPosVector3()));

	// Light // mView, shadow_map, light params
	for (unsigned int i=0; i<lights.size(); ++i)
	{
		fx_m_L_ViewProj =	getFX()->GetVariableByName("m_L_ViewProj")->GetElement(i)->AsMatrix();
		fx_lights =			getFX()->GetVariableByName("lights")->GetElement(i);
		fx_tex_shadow_map = getFX()->GetVariableByName("tex_shadow_map")->GetElement(i)->AsShaderResource();
		
		fx_m_L_ViewProj->SetMatrix(		(float*)&(lights[i]->getViewProjMatrix())	);
		fx_tex_shadow_map->SetResource(	lights[i]->getShadowMapSRV())	;
		fx_lights->SetRawValue(	(void*)&(lights[i]->getLightStructure()), 0, sizeof(Light_Params)	);
	}
	ID3DX11EffectScalarVariable*	fx_shadow_size = getFX()->GetVariableByName("shadow_size")->AsScalar();
	fx_shadow_size->SetInt(lights[0]->getShadowMapSize());
	fx_num_lights->SetInt(lights.size());
	

	fx_bShadowed->SetBool(isShadowReceiver());
	fx_bUsePCSS->SetBool(PCSS());

	ID3DX11EffectScalarVariable*  fx_bLit = getFX()->GetVariableByName("bLit")->AsScalar();
	fx_bLit->SetBool(isLit());
		
	ID3DX11EffectShaderResourceVariable* fx_tex_reflections = getFX()->GetVariableByName("tex_reflections")->AsShaderResource();
	passReflectionSRV(&fx_tex_reflections);

	ID3DX11EffectShaderResourceVariable* fx_tex_refractions = getFX()->GetVariableByName("tex_refractions")->AsShaderResource();
	mRefractor->passRefractionSRV(&fx_tex_refractions);

	XMMATRIX mTex = XMMatrixIdentity();
	ID3DX11EffectMatrixVariable* fx_m_Tex = getFX()->GetVariableByName("m_Tex")->AsMatrix();
	fx_m_Tex->SetMatrix((float*)&mTex);

	if (bSea11)
	{
		context()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST);
		
		UINT stride = sizeof(Vertex); UINT offset = 0;
	
		context()->IASetVertexBuffers(0, 1, &vb_sea11, &stride, &offset);

		ID3DX11EffectTechnique* tech = getFX()->GetTechniqueByName("Sea11");
		tech->GetPassByIndex(0)->Apply(0, context());

		context()->Draw(num_vertices11, 0);

		context()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		
		ID3D11HullShader* hs_null = { 0 };
		context()->HSSetShader(hs_null, 0, 0);

		ID3D11DomainShader* ds_null = { 0 };
		context()->DSSetShader(ds_null, 0, 0);
	}
	else
		Mesh::draw(0, getFX(), "Sea");

	for (unsigned int i=0; i<lights.size(); ++i) {
		fx_tex_shadow_map = getFX()->GetVariableByName("tex_shadow_map")->GetElement(i)->AsShaderResource();
		fx_tex_shadow_map->SetResource(0);
	}
	fx_tex_reflections->SetResource(0);
	fx_tex_refractions->SetResource(0);
	
	ID3DX11EffectTechnique* tech = getFX()->GetTechniqueByName("Sea");
	tech->GetPassByIndex(0)->Apply(0, context());

}


void Sea::init(const char* filename, ID3D11Device* g_device, ID3D11DeviceContext* g_context, int w, int h)
{
	createEffect("../Desert_Island/shaders/sea.fxo", g_device);
	
	PlanarReflector::setWidth(w);
	PlanarReflector::setHeight(h);


	PlanarReflector::init(filename, g_device, g_context);
	_spray.init(g_device, g_context);

	//--------------------------------------------


	mRefractor = new PlanarRefractor();
	mRefractor->setWidth(w);
	mRefractor->setHeight(h);
	mRefractor->init(filename, g_device, g_context);
	

	initSea11();

	fx_m_World				= getFX()->GetVariableByName("m_World")->AsMatrix();
	fx_m_WorldViewProj		= getFX()->GetVariableByName("m_WorldViewProj")->AsMatrix();
	fx_m_L_ViewProj			= getFX()->GetVariableByName("m_L_ViewProj")->AsMatrix();

	fx_tex_shadow_map		= getFX()->GetVariableByName("tex_shadow_map")->AsShaderResource();

	fx_lights				= getFX()->GetVariableByName("lights");

	fx_num_lights			= getFX()->GetVariableByName("num_lights")->AsScalar();

	fx_pEye					= getFX()->GetVariableByName("pEye")->AsVector();
	fx_bShadowed			= getFX()->GetVariableByName("bShadowed")->AsScalar();
	fx_bUsePCSS				= getFX()->GetVariableByName("bUsePCSS")->AsScalar();


	HRESULT hr;

	D3DX11CreateShaderResourceViewFromFile(g_device, L"../Desert_Island/media/h0_normal.bmp", 0, 0, &srv_sea0_normalmap, &hr);
	if (FAILED(hr))
		MessageBoxA(0, "sea heightmap texture load failed", 0, 0);

	D3DX11CreateShaderResourceViewFromFile(g_device, L"../Desert_Island/media/h1_normal.bmp", 0, 0, &srv_sea1_normalmap, &hr);
	if (FAILED(hr))
		MessageBoxA(0, "sea heightmap texture load failed", 0, 0);
	
	D3DX11CreateShaderResourceViewFromFile(g_device, L"../Desert_Island/media/ice_heightmap.png", 0, 0, &srv_ice_heightmap, &hr);
	if (FAILED(hr))
		MessageBoxA(0, "ice texture heightmap load failed", 0, 0);

	D3DX11CreateShaderResourceViewFromFile(g_device, L"../Desert_Island/media/ice.jpg", 0, 0, &srv_ice, &hr);
	if (FAILED(hr))
		MessageBoxA(0, "ice texture load failed", 0, 0);
	

}

void Sea::setFrozen(bool v) 
{ 
	_spray.setActive(false);
	bFrozen = v;
}


void Sea::Update(float dt)
{
	float val = 1.0f;

	if (bFrozen) {
		wave_height -= dt * val;
		wave_height = max(wave_height, 0.0f);

		if (wave_height < 0.001f && ice_percentage < epsilon)
		{
			counter = dt;
			_spray.setActive(false);
		}
		
		if (wave_height < epsilon) {
			ice_percentage = min(counter / 10.0f, 1.0f);
			if (abs(ice_percentage - 1.0f) <  epsilon) {
				bFrozen = false;
				return; 
			}
		}
	}
	else
	{
		ice_percentage -= dt *val;
		ice_percentage = max(ice_percentage, 0.0f);

		if (ice_percentage < 0.001f && wave_height < epsilon)
		{
			counter = dt;
			_spray.setActive(true);
		}

		if (ice_percentage < epsilon)
			wave_height = min(counter / 5.0f,val);
	}

	counter += dt;


}


void Sea::startFog()
{
	if (true == bFog) 
		return;
	
	bFog = true;
	fog_start = counter;
}

void Sea::reset()
{
	bFog = false;
}

void Sea::drawSpray(CXMMATRIX mVP, const XMFLOAT4& pos, float dt)
{
	_spray.draw(mVP, pos, dt);
}

void Sea::setWindPower(float v)
{ 
	if (v > 0.2) 
		_spray.setActive(false); 
	else if (v < 0.2f && !bFrozen)
		_spray.setActive(true); 

	m_windPower = v; 
}


void Sea::initSea11()
{

	float extent = 1.0f;

	// Create vertex buffer
	Vertex vertices[] =	{
								XMFLOAT3(-extent,  0.0f,  extent), XMFLOAT3(0,0,0), XMFLOAT3(0,0,0), XMFLOAT2(0,0), 
								XMFLOAT3( extent,  0.0f,  extent), XMFLOAT3(0,0,0), XMFLOAT3(0,0,0), XMFLOAT2(0,0), 
								XMFLOAT3( extent,  0.0f, -extent), XMFLOAT3(0,0,0), XMFLOAT3(0,0,0), XMFLOAT2(0,0), 
								XMFLOAT3(-extent,  0.0f, -extent), XMFLOAT3(0,0,0), XMFLOAT3(0,0,0), XMFLOAT2(0,0)
							};

	num_vertices11 = 4;

	D3D11_BUFFER_DESC vbdesc;
	vbdesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vbdesc.ByteWidth = sizeof(Vertex) * num_vertices11;
	vbdesc.CPUAccessFlags = vbdesc.MiscFlags = vbdesc.StructureByteStride = 0;
	vbdesc.Usage = D3D11_USAGE_IMMUTABLE;

	D3D11_SUBRESOURCE_DATA vbdata;
	vbdata.SysMemPitch = vbdata.SysMemSlicePitch = 0;
	vbdata.pSysMem = vertices;

	HRESULT hr = g_dev()->CreateBuffer(&vbdesc, &vbdata, &vb_sea11);
	if (FAILED(hr)) MessageBoxA(0, "Error creating sea11 buffer", 0, 0);

}


void Sea::setRadius(float v)
{
	
	radius = max(5.0f, v);

	setScale(radius);

}

void Sea::setWindVector(float v[])
{

	// normalize windDir, and make it 2D (y=0)
	XMFLOAT3 windDir = XMFLOAT3(v[0], 0, v[1]);
	XMVECTOR dir = XMLoadFloat3(&windDir);
	dir = XMVector3Normalize(dir);
	XMStoreFloat3(&m_windDirection, dir);
}